/**
**************************************************************************************
*Palisma - Secrets of the Illuminati is an open-source 2D RPG                        *
*Copyright (C) 2006, Tony Sparks                                                     *
*                                                                                    *
*This library is free software; you can redistribute it and/or                       *
*modify it under the terms of the GNU Lesser General Public                          *
*License as published by the Free Software Foundation; either                        *
*version 2.1 of the License, or (at your option) any later version.                  *
*                                                                                    *
*This library is distributed in the hope that it will be useful,                     *
*but WITHOUT ANY WARRANTY; without even the implied warranty of                      *
*MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU                   *
*Lesser General Public License for more details.                                     *
*                                                                                    *
*You should have received a copy of the GNU Lesser General Public                    *
*License along with this library; if not, write to the Free Software                 *
*Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA      *
**************************************************************************************
*/
#include "StdAfx.h"
#include "EntityManager.h"
#include "IEntity.h"
#include "IWeapon.h"
#include "Player.h"

#include "../shared/StringUtil.h"
#include "../shared/vector.h"
#include "../shared/QuadTree.h"
#include "../render/ViewManager.h"
#include "../kernel.h"

extern Kernel* g_kernel;

#define TIME (g_kernel->GetTime())
// id counter
unsigned int EntityManager::m_idcounter = 1;
// our singleton
EntityManager* EntityManager::instance = new EntityManager;
/**
====================================
EntityManager
====================================
*/
EntityManager::EntityManager(void)
{
    m_player = NULL;
    m_playerCreated = false;

    // temp
    m_scene = NULL;
    m_entityQuadTree = NULL;
}


/**
============================
Get the player
============================
*/
Player* EntityManager::GetPlayer()
{
    // return the player
    return m_player;
}


/**
==================================
Update each entity
==================================
*/
void EntityManager::Update(float deltaTime, ICamera* cam) 
{
    CleanDeadEntities();

    float time = g_kernel->GetTime();     // current time
    
    type_ents entitiesToUpdate;

    Vector2f cameraPosition = cam->GetPosition();
    Rect viewport = cam->GetViewPort();
    Rect r( cameraPosition.x, cameraPosition.y, viewport.width, viewport.height );
    m_entityQuadTree->GetObjects( r, entitiesToUpdate );

    type_ents::iterator it = entitiesToUpdate.begin();
    for(; it != entitiesToUpdate.end(); it++)
    {
        IEntity* ent = (*it);
        ent->m_flags &= ~EF_NOTONSCREEN;        // clear flags for this update
        ent->m_flags &= ~EF_UPDATE;

        //-----TEMP TESTING CAMERA 2
        if ( cam->GetID() == 1  ) { 
            if ( cam->IsAttached( ent ) )
                m_scene->CenterCamera( cam, ent->m_physObject.GetPosition() );
            continue;
        }
        //-----END TEMP

        // make sure the entity is on screen
        if ( !cam->Contains( ent->GetBounds() ) ) { 
            ent->m_flags |= EF_NOTONSCREEN;
            continue;
        }

        if ( time > ent->m_nextUpdate && m_scene ) // temp scene check!
        {
            ent->m_flags |= EF_UPDATE;
            bool popped = m_entityQuadTree->Pop( ent );
            ent->Update( deltaTime );
            Move( ent, cam, deltaTime );
 
            if ( popped ) 
                m_entityQuadTree->Put( ent );
            
        }
    }
    
}

/**
===============================
Move each entity -
===============================
*/
bool EntityManager::Move(IEntity* ent, ICamera* cam, float dt)
{
    // entity is not moving
    if ( !ent->m_direction.Length() && !(ent->GetState() == ENT_STATES[THROWN]) )
        return false;


    // apply gravity -- NEEDS SOME WORK
    if ( !ent->ArrivedToLocation() && ent->m_heightAboveGround )
    {   
        ent->m_gravity += (9.8f * dt);
        ent->m_heightAboveGround -= ent->m_gravity*dt;
        
        if ( ent->m_heightAboveGround <= 0 )
        {
            ent->m_heightAboveGround = 0;
            ent->m_gravity = 0;
        }
    } 
    else
    {
        ent->m_gravity = 0;
        ent->m_heightAboveGround = 0;
        if ( ent->m_flags & EF_MOVED )
        {
            ent->m_direction.ZeroOut();
            ent->m_flags &= ~EF_MOVED; // we are done moving

            Vector2f position = ent->m_physObject.GetPosition();
            return CheckCollision( ent, position );
        }
    }

    Vector2f oldPosition = ent->m_physObject.GetPosition();
    Vector2f impulse = ent->m_direction * ent->m_fSpeed;

    //Vector2f basevelocity ( ent->m_fSpeed, ent->m_fSpeed );
    //Vector2f moveTo = ent->m_direction * basevelocity * dt;
    ent->m_physObject.ApplyImpulse( impulse );
    ent->m_physObject.Update( dt );

    Vector2f moveTo =  ent->m_physObject.GetPosition() - oldPosition;

    // apply gravity
    moveTo.y += ent->m_gravity;

    // get the current map
    Scene* scene = m_scene; // use m_scene here since this is temporary!!!

    // check collisions
    float x = MoveX( ent, moveTo.x, oldPosition, scene );
    float y = MoveY( ent, moveTo.y, oldPosition, scene );
    
    moveTo.Set( x, y );
    ent->m_physObject.SetPosition( moveTo );

    // test to see if we hit a trigger
    scene->InvokeTrigger( ent, moveTo.x, moveTo.y + ent->GetBounds().height/2 );

    // move the camera for the player
    if ( cam->IsAttached( ent ) )
    {
        // center the camera around the player
        scene->CenterCamera( cam, ent->m_physObject.GetPosition() );
    }
    return true;

}


/** Move X coord of an ent */
float EntityManager::MoveX(IEntity *ent, float xmove, Vector2f &oldPosition, Scene* scene)
{
    Rect bounds = ent->GetBounds();
    int midHeight = bounds.height / 2;

    float xToMove = 0.0f;
    while( xmove )
    {
        // move one pixel
        // and test collision
        if ( xmove > 1 )
        {
            xToMove = 1.0f;
            xmove--;
        } 
        else if ( xmove < -1 )
        {
            xToMove = -1.0f;
            xmove++;
        }
        else
        {
            xToMove = xmove;
            xmove = 0.0f;
        }

        oldPosition.x += xToMove;

        // check collision
        if ( scene->Collide( oldPosition.x, oldPosition.y + midHeight, bounds ) ||
             CheckCollision( ent, oldPosition ) )  
        {
            oldPosition.x -= xToMove;
            ent->UpdateCollisionBounds( oldPosition.x, oldPosition.y );
            return oldPosition.x;
        }

    }
    return oldPosition.x;
}

/** Move Y coord of an ent */
float EntityManager::MoveY(IEntity *ent, float ymove, Vector2f &oldPosition, Scene* scene)
{
    Rect bounds = ent->GetBounds();
    int midHeight = bounds.height / 2;

    float yToMove = 0.0f;
    while( ymove )
    {
        // move one pixel
        // and test collision
        if ( ymove > 1 )
        {
            yToMove = 1.0f;
            ymove--;
        } 
        else if ( ymove < -1.0f )
        {
            yToMove = -1.0f;
            ymove++;
        } 
        else
        {
            // we hit 1 pixel
            yToMove = ymove;
            ymove = 0;
        }

        // move ent 1 pixel ahead
        oldPosition.y += yToMove;

        // check collision
        if ( scene->Collide( oldPosition.x, oldPosition.y + midHeight, bounds ) ||
             CheckCollision( ent, oldPosition ) )
        {
            oldPosition.y -= yToMove;
            ent->UpdateCollisionBounds( oldPosition.x, oldPosition.y );
            return oldPosition.y;
        }

    }
    return oldPosition.y;
}

/** Find Entity by Name */
IEntity* EntityManager::Find( const std::string &name )
{
    type_ents::iterator it = entities.begin();
    for(; it != entities.end(); it++)
    {
        // we found the entity
        if ( (*it)->GetName() == name )
            return (*it);
    }
    return NULL;
}


/** Find Entity by id */
IEntity* EntityManager::Find( int id )
{
    type_ents::iterator it = entities.begin();
    for(; it != entities.end(); it++)
    {
        // we found the entity
        if ( (*it)->GetID() == id)
            return (*it);
    }
    return NULL;

}

/** Remove the entity without deleting it */
void EntityManager::RemoveWithoutDeleting(IEntity* ent)
{
    type_ents::iterator it = entities.begin();
    for(; it != entities.end(); it++)
    {
        // we found the entity
        if ( (*it) == ent) {
            m_entityQuadTree->Pop( ent );
            entities.erase( it );
            return;
        }
    }
}

/** Deletes all the entities */
void EntityManager::DeleteAllEntities()
{
    // delete each entity
    type_ents::iterator it = entities.begin();
    while( it != entities.end() )
    {
        IEntity* ent = (*it);
        if ( ent != m_player )
        {
            delete ent;
            it = entities.erase( it );
        }
        else
        {
            ++it;
        }
    }

    if ( m_entityQuadTree ) m_entityQuadTree->Clear();
}

/** Check collision between entities 
 *  Should be O(lg(N)) operation now with m_entityQuadTree
 */
bool EntityManager::CheckCollision(IEntity* ent, const Vector2f &pos )
{
    ent->UpdateCollisionBounds( pos.x, pos.y );
    typedef std::vector< IEntity* > List;
    
    List list;
    m_entityQuadTree->GetObjects( ent->GetBounds(), list );

    List::iterator it = list.begin();
    for (; it != list.end(); ++it )
    {
        IEntity* other = (*it);
        
        // don't collide with yourself
        if ( ent == other ) 
            continue;

        // entity is being erased
        if ( other->CanRemove() )
            continue;

        // if this entity is being carried
        // do not col test
        if ( other->GetState() == ENT_STATES[CARRIED] )
            continue;

        if ( ent->GetState() == ENT_STATES[ THROWN ] )
        {
            // FIXME: Should be something like this: if ( ent->CarriedBy() == other )
            if ( other->GetState() == ENT_STATES[ THROWING ] )
                continue;
        }

        if ( ent->GetBounds().Intersects( other->GetBounds() ) )
        {
            ent->CollideWith( other );
            other->CollideWith( ent );

            return !(other->m_flags & EF_NOCOLLIDE);
        }
    }
    return false;
}


/** Prune the entity List */
void EntityManager::CleanDeadEntities()
{
    type_ents::iterator it = entities.begin();
    while( it != entities.end() )
    {
        IEntity* ent = (*it);

        // release any free entities
        if ( ent->CanRemove() )
        {
            g_kernel->LPrintf(  ("Trying to Delete: " + ent->GetName() ).c_str() );
            g_kernel->LPrintf(  "Before: %d", m_entityQuadTree->Size() );
            if ( !m_entityQuadTree->Pop( ent ) ) 
            {
                g_kernel->LPrintf(  "Could not Pop the entity, continuing!" );
                continue;
            }
            g_kernel->LPrintf(  "After: %d", m_entityQuadTree->Size() );
            g_kernel->LPrintf( "--Deleting from EntityManager:: Releasing %d  bytes", sizeof( (*ent) ) );

			// Send an event stating we have removed this entity 
			IEvent evt( EVT_RM_ENTITY, TIME, new EntityAdditionData( ent ), LT_ADD_RM_ENTITY );
			EventManager::GetInstance()->TriggerEvent( evt );

            delete ent;
            it = entities.erase( it );
        }
        else
        {
            ++it;
        }
    }
}


/** Find the first entity in a given region 
 *  
 */
IEntity* EntityManager::FindInRegion( Rect &region, IEntity* ignoreMe )
{
    if ( m_entityQuadTree )
    {
        typedef std::vector< IEntity* > List;
        
        List list;
        m_entityQuadTree->GetObjects( region, list );

        List::iterator it = list.begin();
        for (; it != list.end(); ++it )
        {
            if ( (*it) == ignoreMe )
                continue;
            if ( region.Intersects( (*it)->GetBounds() ) )
                return (*it);
        }
    }
    return NULL;
}

/** RayCast- Shoot a ray to see if it hits something, if so return
    the first Entity hit, else return NULL.
  */
IEntity* EntityManager::CheckLineHit( const Vector2f &start, const Vector2f &end )
{
    if ( m_scene )
    {
        std::vector< IEntity* > list;
        m_entityQuadTree->GetObjects( start, end, list );

        IEntity* ent = NULL;
        // test for a possible target
        std::vector< IEntity* >::iterator it = list.begin();
        for (; it != list.end(); ++it )
        {   
            IEntity* potential = (*it);
            if ( !(potential->m_flags & EF_NOCOLLIDE) && potential->GetBounds().LineIntersects(start, end) )
            {
                ent = potential;
                break;
            }
        }

        if ( !ent ) 
            return NULL;

        Scene::type_Tiles tiles;
        m_scene->RayTrace( start, end, tiles );

        Vector2f distanceToEntity( start - ent->m_physObject.GetPosition() );
        Scene::type_Tiles::iterator it2 = tiles.begin();
        for(; it2 != tiles.end(); ++it2 )
        {
            if ( (*it2)->GetBounds().LineIntersects( start, end ) )
            {
                Rect tileBounds = (*it2)->GetBounds();
                Vector2f tileLocation ( tileBounds.x, tileBounds.y ); 
                Vector2f distanceToObject( start - tileLocation );

                if ( distanceToObject.Length() < distanceToEntity.Length() )
                {
                    return NULL;
                }
            }
        }
        return ent;

    }
    return NULL;
}


/** HACK--How to register the Map with entities?
    Reference: Currently I register the scene
    when we load a new Scene, look at InGameStateView::LoadScene()
    with the EntityManager, this is horrible and NEEDS fixing.
  */
void    EntityManager::SetScene(Scene* scene) 
{ 
    if ( m_entityQuadTree ) 
        delete m_entityQuadTree;

    int w = 0, 
        h = 0;    
    m_scene = scene;
    m_scene->TileToWorld( m_scene->GetMaxX(), m_scene->GetMaxY(), w, h );
    
    m_entityQuadTree = new QuadTree<IEntity*>( 64,64, w, h  );

    // Hacks -- should we make the m_entityQuadTrees globals?
    //ViewManager::GetInstance()->SetEntityList( m_entityQuadTree );
}
/** Place the Entity */
void EntityManager::PutInWorld( IEntity* ent )
{
    /*
        Place in the m_entityQuadTree
     */
    if ( m_entityQuadTree )
    {
        m_entityQuadTree->Pop( ent );
        m_entityQuadTree->Put( ent );
    }
}

/** Register the entity */
void EntityManager::RegisterEntity( IEntity* ent, const std::string &entName ) 
{ 
    if ( ent->GetType() == TYPE_PLAYER )
    {
        m_player = static_cast<Player*>( ent );
        m_player->SetID( 0 );   // the player has the special id of 0
    }
    else
    {
        ent->SetID ( m_idcounter++ );   // set the id
		// Send an event stating we have added this entity 
		// NOTE: For now, do not add the player
		IEvent evt( EVT_ADD_ENTITY, TIME, new EntityAdditionData( ent ), LT_ADD_RM_ENTITY );
		EventManager::GetInstance()->TriggerEvent( evt );
    }

    AddEntity( ent );
    ent->SetName( entName );
};

EntityManager::~EntityManager(void)
{
    DeleteAllEntities();

    // delete this instance
	if ( instance ) {
		delete instance;
		instance = NULL;
	}
    // delete the player
    if ( m_playerCreated )
    {
        if ( m_player )
            delete m_player;
    }
}
